package com.netty.demo.server.utils;

import lombok.extern.slf4j.Slf4j;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.Inflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

/**
 * gzip压缩
 *
 * @author 余秀良
 * @version 1.0
 * <p>date: 2019年09月09日  13:29 </p>
 * <p>address: 四川成都 </p>
 */
@Slf4j
public final class ZipUtil {
    private static byte[] ZIP_HEADER_1 = new byte[]{80, 75, 3, 4};
    private static byte[] ZIP_HEADER_2 = new byte[]{80, 75, 5, 6};
    /**
     * 压缩得到的文件的后缀名
     */
    private static final String SUFFIX = ".zip";

    private ZipUtil() {
        throw new UnsupportedOperationException("这是一个工具类，不能实例化");
    }

    /**
     * 判断文件是否为一个压缩文件
     *
     * @param file 要验证的文件
     * @return 是否是压缩文件
     */
    public static boolean isZipFile(File file) {
        if (file == null) {
            return false;
        }
        if (file.isDirectory()) {
            return false;
        }
        boolean isArchive = false;
        InputStream input = null;
        try {
            input = new FileInputStream(file);
            byte[] buffer = new byte[4];
            int length = input.read(buffer, 0, 4);
            int l = 4;
            if (length == l) {
                isArchive = (Arrays.equals(ZIP_HEADER_1, buffer)) || (Arrays.equals(ZIP_HEADER_2, buffer));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return isArchive;
    }

    /**
     * 压缩
     *
     * @param data 待压缩的数据
     * @return 压缩以后的数据
     * @throws IOException IOException
     */
    public static byte[] dZip(byte[] data) throws IOException {
        byte[] output = new byte[Integer.sum(data.length + 10, Double.valueOf(Math.ceil(data.length * 0.25f)).intValue())];
        //压缩级别
        Deflater deflater = new Deflater(9);
        deflater.setInput(data);
        deflater.finish();
        int deflate = deflater.deflate(output);
        deflater.end();
        return Arrays.copyOf(output, deflate);
    }

    /**
     * 压缩
     *
     * @param file 待压缩的文件
     * @return 压缩以后的数据
     * @throws IOException IOException
     */
    public static byte[] dZip(File file) throws IOException {
        FileInputStream in = null;
        try {
            in = new FileInputStream(file);
            byte[] data = new byte[in.available()];
            return dZip(data);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            assert in != null;
            in.close();
        }
        return null;
    }

    /**
     * 解压
     *
     * @param data 待解压的数据
     * @return 解压后的数据
     * @throws DataFormatException DataFormatException
     */
    public static byte[] undZip(byte[] data) throws DataFormatException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        Inflater decompressor = new Inflater();
        try {
            decompressor.setInput(data);
            final byte[] buf = new byte[2048];
            while (!decompressor.finished()) {
                int count = decompressor.inflate(buf);
                bos.write(buf, 0, count);
            }
        } finally {
            decompressor.end();
        }
        return bos.toByteArray();
    }

    /**
     * 压缩
     *
     * @param data 待压缩的数据
     * @return 压缩以后的数据
     * @throws Exception Exception
     */
    public static byte[] gzip(byte[] data) throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        GZIPOutputStream gzip = new GZIPOutputStream(bos);
        gzip.write(data);
        gzip.finish();
        gzip.close();
        byte[] ret = bos.toByteArray();
        bos.close();
        return ret;
    }

    /**
     * 压缩
     *
     * @param file 待压缩的文件
     * @return 压缩以后的数据
     * @throws Exception Exception
     */
    public static byte[] gzip(File file) throws Exception {
        FileInputStream in = null;
        try {
            in = new FileInputStream(file);
            byte[] data = new byte[in.available()];
            return gzip(data);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            assert in != null;
            in.close();
        }
        return null;
    }

    /**
     * @param data 解压
     * @return byte[]
     * @throws Exception Exception
     */
    public static byte[] ungZip(byte[] data) throws Exception {
        ByteArrayInputStream bis = new ByteArrayInputStream(data);
        GZIPInputStream gzip = new GZIPInputStream(bis);
        byte[] buf = new byte[2048];
        int num;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        while ((num = gzip.read(buf, 0, buf.length)) != -1) {
            bos.write(buf, 0, num);
        }
        gzip.close();
        bis.close();
        byte[] ret = bos.toByteArray();
        bos.flush();
        bos.close();
        return ret;
    }

    /**
     * 压缩文件为 zip 包
     *
     * @param src     需要压缩的文件路径
     * @param out     存放zip 包的路径
     * @param zipName 压缩后的文件名
     * @author 余秀良
     * @version 1.0
     * <p>  date: 2019 - 09 - 17  20:23    </p>
     * <p>  address: 四川成都  </p>
     */
    public static void zip(String src, String out, String zipName) {
        FileOutputStream fileOutputStream = null;
        ZipOutputStream zipOutputStream = null;
        FileInputStream fileInputStream = null;
        try {
            File file = new File(src);
            if (!file.exists()) {
                log.error("需要压缩的文件 {} 不存在", src);
                return;
            }
            File zipPath = new File(out);
            if (!zipPath.exists()) {
                boolean mkdirs = zipPath.mkdirs();
                log.info("创建目录{}  {}", zipPath.getPath(), mkdirs);
            }
            if (file.isDirectory()) {
                zipFileByDirectory(src, out, zipName);
                return;
            }
            String fp = out + File.separator + zipName + SUFFIX;
            File zipFile = new File(fp);
            fileOutputStream = new FileOutputStream(zipFile);
            zipOutputStream = new ZipOutputStream(fileOutputStream);

            ZipEntry zipEntry = new ZipEntry(file.getName());
            zipEntry.setSize(file.length());
            zipEntry.setTime(file.lastModified());


            zipOutputStream.putNextEntry(zipEntry);

            fileInputStream = new FileInputStream(file);
            byte[] buffer = new byte[2048];
            int len;
            while ((len = fileInputStream.read(buffer)) != -1) {
                zipOutputStream.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            closeStream(fileOutputStream, zipOutputStream, fileInputStream);
        }
    }

    /**
     * 解压文件
     *
     * @param zipName   需要解压的文件
     * @param unZipPath 保存解压后的文件目录
     * @author 余秀良
     * @version 1.0
     * <p>  date: 2019 - 09 - 18  15:02    </p>
     * <p>  address: 四川成都  </p>
     */
    public static void unZip(String zipName, String unZipPath) {
        log.info("开始解压");
        Path targetDirPath = Paths.get(unZipPath + File.separator + zipName.substring(zipName.lastIndexOf(File.separator) + 1, zipName.lastIndexOf(".")));
        try (ZipFile zipFile = new ZipFile(zipName)) {
            zipFile.stream()
                    .parallel() // 启用多线程
                    .forEach(entry -> {
                        try {
                            Path targetPath = targetDirPath.resolve(Paths.get(entry.getName()));
                            if (entry.isDirectory()) {
                                if (!targetPath.toFile().exists()) {
                                    Files.createDirectories(targetPath);
                                }
                            } else {
                                if (Files.notExists(targetPath.getParent())) {
                                    Files.createDirectories(targetPath.getParent());
                                }
                                try (InputStream in = zipFile.getInputStream(entry)) {
                                    Files.copy(in, targetPath, StandardCopyOption.REPLACE_EXISTING);
                                }
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                            log.error("处理zip文件条目是出错  \n {} \n {}", entry.getName(), e.getMessage());
                        }
                    });
            System.out.println("解压结束");
        } catch (IOException e) {
            throw new RuntimeException("打开zip文件出错 '" + zipName + "': " + e, e);
        }
    }

    /**
     * 删除指定文件
     *
     * @param filePath 需要删除的文件位置
     * @author 余秀良
     * @version 1.0
     * <p>  date: 2019 - 09 - 17  20:41    </p>
     * <p>  address: 四川成都  </p>
     */
    public static void deleteFile(String filePath) {
        File file = new File(filePath);
        if (!file.exists()) {
            return;
        }
        delete(file);
    }

    private static void delete(File file) {
        if (!file.exists()) {
            log.error("需要删除的文件 {} 不存在", file.getPath());
            return;
        }
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File value : Objects.requireNonNull(files)) {
                delete(value);
            }
        }
        boolean delete = file.delete();
        log.info("删除文件 {} {}", file.getPath(), delete);
    }

    /**
     * 压缩文件
     *
     * @param src 待压缩的文件目录
     * @param out 压缩后保存的目录
     * @return 压缩文件保存目录
     * @author 余秀良
     * @version 1.0
     * <p>  date: 2019 - 09 - 18  09:09    </p>
     * <p>  address: 四川成都  </p>
     */
    public static String zip(String src, String out) {
        String zipPath = null;
        try {
            File fileToZip = new File(src);
            if (fileToZip.exists()) {
                File zipDir = new File(out);
                if (!zipDir.exists()) {
                    boolean mkdirs = zipDir.mkdirs();
                    log.info("创建文件夹 {} 状态{} ", zipDir.getPath(), mkdirs);
                }
                if (fileToZip.isDirectory()) {
                    String zipName = src.substring(src.lastIndexOf(File.separator) + 1);
                    zipPath = out + File.separator + zipName + SUFFIX;
                    Files.deleteIfExists(Paths.get(zipPath));
                    zipFileByDirectory(src, out, zipName);
                } else {
                    String zipName = src.substring(src.lastIndexOf(File.separator) + 1, src.lastIndexOf("."));
                    zipPath = out + File.separator + zipName + SUFFIX;
                    Files.deleteIfExists(Paths.get(zipPath));
                    zip(src, out, zipName);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return zipPath;
    }

    /**
     * 压缩多文件
     *
     * @param sourceFilePathList 待压缩的文件目录
     * @param zipFilePath        保存压缩文件的目录
     * @param zipName            压缩文件的名字
     * @return 压缩文件保存的目录
     * @author 余秀良
     * @version 1.0
     * <p>  date: 2019 - 09 - 18  10:10    </p>
     * <p>  address: 四川成都  </p>
     */
    public static String zip(List<String> sourceFilePathList, String zipFilePath, String zipName) {
        FileOutputStream fileOutputStream = null;
        ZipOutputStream zipOutputStream = null;
        try {
            File zipDir = new File(zipFilePath);
            if (!zipDir.exists()) {
                boolean mkdirs = zipDir.mkdirs();
                log.info("文件夹创建状态{} ", mkdirs);
            }
            zipFilePath = zipFilePath + File.separator + zipName + SUFFIX;
            fileOutputStream = new FileOutputStream(zipFilePath);
            zipOutputStream = new ZipOutputStream(fileOutputStream);
            for (String srcFile : sourceFilePathList) {
                File fileToZip = new File(srcFile);
                if (fileToZip.exists()) {
                    FileInputStream fileInputStream = new FileInputStream(fileToZip);
                    ZipEntry zipEntry = new ZipEntry(fileToZip.getName());
                    zipOutputStream.putNextEntry(zipEntry);
                    byte[] bytes = new byte[2048];
                    int length;
                    while ((length = fileInputStream.read(bytes)) >= 0) {
                        zipOutputStream.write(bytes, 0, length);
                    }
                    fileInputStream.close();
                }
            }
        } catch (IOException e) {
            log.error("压缩多文件出错{}{}", e.getMessage(), e.getLocalizedMessage());
            e.printStackTrace();
        } finally {
            closeStream(fileOutputStream, zipOutputStream, null);
        }
        return zipFilePath;
    }

    /**
     * 压缩文件夹
     *
     * @param sourceFilePath 需要压缩的目录
     * @param zipFilePath    保存压缩文件的目录
     * @param zipName        压缩文件名称
     * @return {@link String}
     * @author 余秀良
     * @version 1.0
     * <p>  date: 2019 - 09 - 18  14:46    </p>
     * <p>  address: 四川成都  </p>
     */
    private static String zipFileByDirectory(String sourceFilePath, String zipFilePath, String zipName) {
        File fileToZip = new File(sourceFilePath);
        FileOutputStream fileOutputStream = null;
        ZipOutputStream zipOutputStream = null;
        String zipPath = null;
        try {
            if (fileToZip.exists()) {
                File file = new File(zipFilePath);
                if (!file.exists()) {
                    boolean mkdirs = file.mkdirs();
                    log.info("保存压缩文件的路径 {} 创建 {}", zipFilePath, mkdirs);
                }
                zipPath = zipFilePath + File.separator + zipName + SUFFIX;
                fileOutputStream = new FileOutputStream(zipPath);
                zipOutputStream = new ZipOutputStream(fileOutputStream);
                zipDirectory(fileToZip, fileToZip.getName(), zipOutputStream);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (zipOutputStream != null) {
                    zipOutputStream.close();
                }
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return zipPath;
    }

    /**
     * 压缩文件夹
     *
     * @param fileToZip       要压缩的文件
     * @param fileName        文件名
     * @param zipOutputStream ZipOutputStream
     * @author 余秀良
     * @version 1.0
     * <p>  date: 2019 - 09 - 18  14:45    </p>
     * <p>  address: 四川成都  </p>
     */
    private static void zipDirectory(File fileToZip, String fileName, ZipOutputStream zipOutputStream) {
        FileInputStream fileInputStream = null;
        try {
            if (fileToZip.isHidden()) {
                return;
            }
            if (fileToZip.isDirectory()) {
                File[] children = fileToZip.listFiles();
                if (children != null) {
                    for (File childFile : children) {
                        zipDirectory(childFile, fileName + File.separator + childFile.getName(), zipOutputStream);
                    }
                }
                return;
            }
            fileInputStream = new FileInputStream(fileToZip);
            ZipEntry zipEntry = new ZipEntry(fileName);
            zipOutputStream.putNextEntry(zipEntry);
            byte[] bytes = new byte[2048];
            int length;
            while ((length = fileInputStream.read(bytes)) >= 0) {
                zipOutputStream.write(bytes, 0, length);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 关闭流
     *
     * @param fileOutputStream FileOutputStream
     * @param zipOutputStream  ZipOutputStream
     * @param fileInputStream  FileInputStream
     * @author 余秀良
     * @version 1.0
     * <p>  date: 2019 - 09 - 18  09:03    </p>
     * <p>  address: 四川成都  </p>
     */
    private static void closeStream(FileOutputStream fileOutputStream, ZipOutputStream zipOutputStream, FileInputStream fileInputStream) {
        try {
            if (zipOutputStream != null) {
                zipOutputStream.closeEntry();
                zipOutputStream.close();
            }
            if (fileInputStream != null) {
                fileInputStream.close();
            }
            if (fileOutputStream != null) {
                fileOutputStream.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
